package service

import (
	"context"
	"fmt"
	"fx_swift_forex/api"
	"fx_swift_forex/internal/model"
	"fx_swift_forex/pkg/code"
	"fx_swift_forex/pkg/crypt"
	"fx_swift_forex/pkg/global"
	"fx_swift_forex/pkg/internalerrors"
	"fx_swift_forex/pkg/jwt"
	"fx_swift_forex/pkg/logger"
	"fx_swift_forex/pkg/mail"
	"fx_swift_forex/pkg/temple"
	"fx_swift_forex/pkg/utils"
	"github.com/gogf/gf/v2/container/gvar"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
	"time"
)

type ILoginSrv interface {
	Register(ctx context.Context, req *api.RegisterReq) (*api.LoginRes, error)

	Login(ctx context.Context, req *api.LoginReq) (*api.LoginRes, error)

	SendMailCode(ctx context.Context, mail string) error

	ForgetPassword(ctx context.Context, req *api.ForgetPasswordReq) error

	UpdatePassword(ctx context.Context, req *api.UpdatePasswordReq) error

	SignOut(ctx context.Context) error

	GetAgentInfo(ctx context.Context) (*api.GetAgentInfoRes, error)

	EmailIsExit(ctx context.Context, email string) (bool, error)

	SetCurrency(ctx context.Context, currency string) error

	DeactivateAccount(ctx context.Context) error
}

type loginSrv struct {
}

func NewLoginSrv() ILoginSrv {
	return &loginSrv{}
}

// 注册
func (l *loginSrv) Register(ctx context.Context, req *api.RegisterReq) (*api.LoginRes, error) {
	var (
		err            error
		hashedPassword string
		salt           string
		num            int
		redisResult    *gvar.Var
		redisCodes     string
		account        string
		token          string
	)
	// 比对验证码
	redisResult, err = g.Redis().Get(ctx, req.Email)
	if err != nil {
		logger.SugarLogger.Error(err)
		return nil, internalerrors.New(code.ErrUnknown, "验证码不正确")
	}
	if redisResult.Val() != nil {
		redisCodes = redisResult.String()
		if redisCodes != req.Code {
			return nil, internalerrors.New(code.ErrUnknown, "验证码不正确")
		}
	} else {
		return nil, internalerrors.New(code.ErrUnknown, "验证码不正确")
	}

	// 查询邮箱是否被注册
	if num, err = global.GFDB.Ctx(ctx).Model("forex_user").Where("email", req.Email).Count(); err != nil || num == 1 {
		return nil, internalerrors.New(code.ErrUnknown, "邮箱已经被注册了")
	}

	if hashedPassword, salt, err = crypt.HashPassword(req.Password); err != nil {
		return nil, internalerrors.New(code.ErrUnknown, err.Error())
	}

	account = utils.GetHashCode(utils.GenerateCode32())
	err = global.GFDB.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		if _, err = tx.Insert("forex_user", &model.ForexUser{
			Account:         account,
			AgentNo:         "F" + fmt.Sprint(time.Now().UnixNano()/int64(time.Millisecond)),
			Email:           req.Email,
			Password:        hashedPassword,
			Salt:            salt,
			AccountType:     req.AccountType,
			Country:         req.Country,
			DefaultCurrency: "USD",
		}); err != nil {
			logger.SugarLogger.Error(err)
			return internalerrors.New(code.ErrUnknown, "注册失败")
		}
		return nil
	})
	if err == nil {
		// generate token
		if token, err = jwt.Gen(account, req.Email, req.AccountType, ""); err != nil {
			return nil, internalerrors.New(code.ErrUnknown, "token generate failed")
		}

		// set token to redis
		if err = g.Redis().SetEX(ctx, account, token, global.ViperObj.GetInt64("token.expired_time")); err != nil {
			return nil, internalerrors.New(code.ErrUnknown, "token keep failed")
		}
	}
	return &api.LoginRes{
		Token:   token,
		Email:   req.Email,
		Account: account,
	}, nil
}

// 登陆
func (l *loginSrv) Login(ctx context.Context, req *api.LoginReq) (*api.LoginRes, error) {
	var (
		err     error
		token   string
		userObj *model.ForexUser
	)
	// 根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("forex_user").Where("email=?", req.Email).
		Scan(&userObj); err != nil {
		logger.SugarLogger.Error(err)
		return nil, internalerrors.New(code.ErrUnknown, "该帐号不存在")
	}

	if userObj == nil {
		return nil, internalerrors.New(code.ErrUnknown, "该帐号不存在")
	}

	// compare password
	if err = userObj.ComparePassword(req.Password); err != nil {
		return nil, internalerrors.New(code.ErrPasswordIncorrect, "密码不匹配")
	}

	//获取当前账号KYC信息
	customerObj := &model.ForexOnboardingCustomer{}
	if err = global.GFDB.Model("forex_onboarding_customer").Where("account", userObj.Account).OrderDesc("created_at").Limit(1).Scan(&customerObj); err != nil {
		logger.SugarLogger.Error(err)
	}
	var customerId = ""
	var customerStatus = ""
	var screenResult = ""
	if customerObj != nil {
		customerId = customerObj.CustomerId
		customerStatus = customerObj.CustomerStatus
		screenResult = customerObj.ScreeningResult
		if err = g.Redis().SetEX(ctx, userObj.Account+"_customerId", customerId, 174600); err != nil {
			return nil, internalerrors.New(code.ErrUnknown, "customerId keep failed")
		}
	}
	// generate token
	if token, err = jwt.Gen(userObj.Account, userObj.Email, userObj.AccountType, customerId); err != nil {
		return nil, internalerrors.New(code.ErrUnknown, "token generate failed")
	}

	// set token to redis
	if err = g.Redis().SetEX(ctx, userObj.Account, token, global.ViperObj.GetInt64("token.expired_time")); err != nil {
		return nil, internalerrors.New(code.ErrUnknown, "token keep failed")
	}

	return &api.LoginRes{
		Token:           token,
		Email:           userObj.Email,
		Account:         userObj.Account,
		AccountType:     userObj.AccountType,
		Country:         userObj.Country,
		AgentNo:         userObj.AgentNo,
		CustomerStatus:  customerStatus,
		CustomerId:      customerId,
		ScreenResult:    screenResult,
		DefaultCurrency: userObj.DefaultCurrency,
	}, nil
}

// 退出登陆
func (l *loginSrv) SignOut(ctx context.Context) error {
	var (
		err error
		//resultNums int64
		//customerNums int64
		accountToken string
	)

	accountToken = ctx.Value("Account").(string)
	//清除customerId信息
	_, err = g.Redis().Del(ctx, accountToken+"_customerId")
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "清除customerId信息异常")
	}

	//清除account的token信息
	_, err = g.Redis().Del(ctx, accountToken)
	if err != nil {
		return internalerrors.New(code.ErrBind, "帐号异常")
	}

	return err
}

// 发送code到邮箱
func (l *loginSrv) SendMailCode(ctx context.Context, email string) error {
	var (
		err error
	)
	defer func() {
		if r := recover(); r != nil {
			logger.SugarLogger.Error(err)
		}
	}()
	// generate random validation code
	codes := utils.GenerateVerificationCode(6)

	if !VerifyEmailFormat(email) {
		return internalerrors.New(code.ErrUnknown, "邮箱格式不正确")
	}

	// 存入redis中并且设置过期时间
	if err = g.Redis().SetEX(ctx, email, codes, 600); err != nil {
		return internalerrors.New(code.ErrUnknown, "验证码发送失败")
	}
	go mail.SendMail(email, temple.SendMailHtmlStr, "邮箱验证码", codes)

	return err
}

// 忘记密码
func (l *loginSrv) ForgetPassword(ctx context.Context, req *api.ForgetPasswordReq) error {
	var (
		userObj              *model.ForexUser
		err                  error
		redisResult          *gvar.Var
		redisCodes           string
		hashedPassword, salt string
	)

	// 根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("forex_user").Where("email=?", req.Email).
		Scan(&userObj); err != nil {
		return internalerrors.New(code.ErrBind, "帐号查询异常")
	}
	if userObj == nil {
		return internalerrors.New(code.ErrUnknown, "请先去注册")
	}
	// 比对验证码
	redisResult, err = g.Redis().Get(ctx, userObj.Email)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "验证码不正确")
	}
	if redisResult.Val() != nil {
		redisCodes = redisResult.String()
		if redisCodes != req.Code {
			return internalerrors.New(code.ErrUnknown, "验证码不正确")
		}
	} else {
		return internalerrors.New(code.ErrUnknown, "验证码不正确")
	}
	// update password
	hashedPassword, salt, err = crypt.HashPassword(req.NewPassword)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	_, err = global.GFDB.Model("forex_user").Data(g.Map{"password": hashedPassword, "salt": salt}).
		Where("account=?", userObj.Account).Update()
	return nil
}

// 更新密码
func (l *loginSrv) UpdatePassword(ctx context.Context, req *api.UpdatePasswordReq) error {
	var (
		userObj *model.ForexUser
		err     error
		account string
	)
	account = ctx.Value("Account").(string)
	// 根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("forex_user").Where("account=?", account).
		Scan(&userObj); err != nil || userObj == nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	if err = userObj.ComparePassword(req.OldPassword); err != nil {
		return internalerrors.New(code.ErrPasswordIncorrect, "password not match")
	}
	hashedPassword, salt, err := crypt.HashPassword(req.NewPassword)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	_, err = global.GFDB.Model("forex_user").Data(g.Map{"password": hashedPassword, "salt": salt}).
		Where("account=?", account).Update()
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	return nil
}

// 获取登陆用户信息
func (l *loginSrv) GetAgentInfo(ctx context.Context) (*api.GetAgentInfoRes, error) {
	var (
		err        error
		account    string
		customerId string
		userObj    *model.ForexUser
	)
	account = ctx.Value("Account").(string)
	//customerId = ctx.Value("CustomerId").(string)
	customerId, err = GetRedisCustomerId(ctx)
	//根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("forex_user").Where("account=?", account).
		Scan(&userObj); err != nil || userObj == nil {
		logger.SugarLogger.Error(err)
		panic(internalerrors.New(code.ErrBind, "该帐号存在异常"))
	}

	//获取当前账号KYC状态信息
	customerObj := &model.ForexOnboardingCustomer{}
	if customerId != "" {
		if err = global.GFDB.Model("forex_onboarding_customer").Where("account=? and customer_id=?", account, customerId).Scan(&customerObj); err != nil {
			logger.SugarLogger.Error(err)
		}
	}

	//}

	if customerObj == nil {
		logger.SugarLogger.Error(err)
		customerObj.CustomerStatus = ""
		customerObj.CustomerId = ""
		customerObj.ScreeningResult = ""
	}
	return &api.GetAgentInfoRes{
		Email:           userObj.Email,
		Account:         userObj.Account,
		AccountType:     userObj.AccountType,
		Country:         userObj.Country,
		DefaultCurrency: userObj.DefaultCurrency,
		AgentNo:         userObj.AgentNo,
		CustomerId:      customerObj.CustomerId,
		CustomerStatus:  customerObj.CustomerStatus,
		ScreenResult:    customerObj.ScreeningResult,
	}, nil
}

// 判断邮箱是否存在
func (l *loginSrv) EmailIsExit(ctx context.Context, email string) (bool, error) {
	var (
		err error
		num int
	)
	num, err = global.GFDB.Ctx(ctx).Model("forex_user").Where("email=?", email).Count()
	if err != nil {
		return false, internalerrors.New(code.ErrUnknown, "查询数据有误")
	}
	if num == 1 {
		return true, err
	}
	return false, err
}

// 设置币种
func (l *loginSrv) SetCurrency(ctx context.Context, currency string) error {
	var (
		err error
	)
	account := ctx.Value("Account").(string)
	if currency == "" {
		return internalerrors.New(code.ErrUnknown, "币种不能为空")
	}
	_, err = global.GFDB.Model("forex_user").Data(g.Map{"default_currency": currency}).
		Where("account=?", account).Update()
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "设置币种失败")
	}
	return nil
}

// 注销账户
func (l *loginSrv) DeactivateAccount(ctx context.Context) error {
	var (
		err          error
		contractsObj *model.ForexContacts
	)

	customerId, err := GetRedisCustomerId(ctx)
	//获取账户联系人id，用于查询账户余额
	if err = global.GFDB.Model("forex_contacts").Where("your_reference=? and status=?", customerId, "enabled").Scan(&contractsObj); err != nil {
		return internalerrors.New(code.ErrUnknown, "数据查询错误")
	}
	if contractsObj == nil {
		return internalerrors.New(code.ErrUnknown, "没有账户记录")
	}
	//TODO 判断该账户余额是否为0，为0则直接注销，否则提示用户先进行额度清零

	return err
}
